// Main script for module 'Tween'

_TweenData _tweens[MAX_TWEENS];

///////////////////////////////////////////////////////////////////////////////

int SecondsToLoops(float timeInSeconds) {
  return FloatToInt(IntToFloat(GetGameSpeed()) * timeInSeconds, eRoundNearest);
}

int Lerp(int from, int to, float amount) {
  return FloatToInt(IntToFloat(from) + IntToFloat(to - from) * amount, eRoundUp);
}

float ComputeTiming(int currentTime, int duration, TweenTiming timingType) {
	float timing = IntToFloat(currentTime) / IntToFloat(duration);
	
	if (timingType != eLinearTween) {
	  float timing2 = Maths.RaiseToPower(timing, 2.0);
	  
		if (timingType == eEaseInTween) {
		  timing = ((3.0 * timing2) - (timing2 * timing)) * 0.5;
		}
		else if (timingType == eEaseOutTween) {
		  timing = ((3.0 * timing) - (timing2 * timing)) * 0.5;
		}
		else if (timingType == eEaseInEaseOutTween) {
		  timing = (3.0 * timing2) - (2.0 * timing * timing2);
		}
	}
	
	return timing;
}

///////////////////////////////////////////////////////////////////////////////

int NewTween(_TweenType type, float timeInSeconds, short toX, short toY, short fromX, short fromY,
						GUI* guiRef, Object* objectRef, Character* characterRef, TweenTiming timing, TweenStyle style) {
	
	short i = 0;
	short spotAvailable = -1;
	
	while (i < MAX_TWEENS && spotAvailable == -1) {
	  if (_tweens[i].duration == -1) {
			spotAvailable = i;
		}
	  
	  i++;
  }
  
  if (spotAvailable != -1) {
    _tweens[spotAvailable].type = type;
		_tweens[spotAvailable].guiRef = guiRef;
		_tweens[spotAvailable].objectRef = objectRef;
		_tweens[spotAvailable].characterRef = characterRef;
		_tweens[spotAvailable].toX = toX;
		_tweens[spotAvailable].toY = toY;
		_tweens[spotAvailable].fromX = fromX;
		_tweens[spotAvailable].fromY = fromY;
		_tweens[spotAvailable].duration = SecondsToLoops(timeInSeconds);
		_tweens[spotAvailable].frameCount = 0;
		_tweens[spotAvailable].timing = timing;
		_tweens[spotAvailable].style = style;
		
		if (_tweens[spotAvailable].style == eBlockTween) {
		  Wait(_tweens[spotAvailable].duration + 1);
		}
		else {
			return _tweens[spotAvailable].duration + 1;
		}
  }
#ifdef DEBUG
  else {
    AbortGame("Cannot create new tween because the Tween module is currently playing %d tween(s), which is the maximum. You can increase this max number on the Tween module script header.", MAX_TWEENS);
  }
#endif
  

	return 1;	
}

int TweenGUIPosition(GUI* guiRef, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
  return NewTween(_eTweenGUIPosition, timeInSeconds, toX, toY, guiRef.X, guiRef.Y, guiRef, null, null, timing, style);
}

int TweenGUITransparency(GUI* guiRef, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
	return NewTween(_eTweenGUITransparency, timeInSeconds, toTransparency, 0, guiRef.Transparency, 0, guiRef, null, null, timing, style);
}

int TweenGUISize(GUI* guiRef, float timeInSeconds, short toWidth, short toHeight, TweenTiming timing, TweenStyle style) {
	return NewTween(_eTweenGUISize, timeInSeconds, toWidth, toHeight, guiRef.Width, guiRef.Height, guiRef, null, null, timing, style);
}

int TweenObjectPosition(Object* objectRef, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
	return NewTween(_eTweenObjectPosition, timeInSeconds, toX, toY, objectRef.X, objectRef.Y, null, objectRef, null, timing, style);
}

int TweenObjectTransparency(Object* objectRef, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
	return NewTween(_eTweenObjectTransparency, timeInSeconds, toTransparency, 0, objectRef.Transparency, 0, null, objectRef, null, timing, style);
}

int TweenCharacterPosition(Character* characterRef, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
  return NewTween(_eTweenCharacterPosition, timeInSeconds, toX, toY, characterRef.x, characterRef.y, null, null, characterRef, timing, style);
}

int TweenCharacterScaling(Character* characterRef, float timeInSeconds, short toScaling, TweenTiming timing, TweenStyle style) {
	if (!characterRef.ManualScaling) {
		characterRef.ManualScaling = true;
	}
	  
  return NewTween(_eTweenCharacterScaling, timeInSeconds, toScaling, 0, characterRef.Scaling, 0, null, null, characterRef, timing, style);
}

int TweenCharacterTransparency(Character* characterRef, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
  return NewTween(_eTweenCharacterTransparency, timeInSeconds, toTransparency, 0, characterRef.Transparency, 0, null, null, characterRef, timing, style);
}

void TweenStopAllForGUI(GUI* guiRef) {
  short i = 0;
  
	while (i < MAX_TWEENS) {
	  if (_tweens[i].guiRef == guiRef) {
			_tweens[i].duration = -1;
    }
    
		i++;
	}
}

void TweenStopAllForObject(Object* objectRef) {
  short i = 0;
  
	while (i < MAX_TWEENS) {
	  if (_tweens[i].objectRef == objectRef) {
			_tweens[i].duration = -1;
    }
    
		i++;
	}
}

void TweenStopAllForCharacter(Character* characterRef) {
  short i = 0;
  
	while (i < MAX_TWEENS) {
	  if (_tweens[i].characterRef == characterRef) {
			_tweens[i].duration = -1;
    }
    
		i++;
	}
}

void TweenStopAll() {
  short i = 0;
  
  while (i < MAX_TWEENS) {
    _tweens[i].duration = -1;
    i++;
  }
}

///////////////////////////////////////////////////////////////////////////////
// FOR AGS 3.0 OR LATER ONLY
///////////////////////////////////////////////////////////////////////////////

#ifdef AGS_SUPPORTS_IFVER
#ifver 3.0
int TweenPosition(this GUI*, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
  return TweenGUIPosition(this, timeInSeconds, toX, toY, timing, style);
}

int TweenPosition(this Object*, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
  return TweenObjectPosition(this, timeInSeconds, toX, toY, timing, style);
}

int TweenPosition(this Character*, float timeInSeconds, short toX, short toY, TweenTiming timing, TweenStyle style) {
  return TweenCharacterPosition(this, timeInSeconds, toX, toY, timing, style);
}

int TweenTransparency(this GUI*, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
  return TweenGUITransparency(this, timeInSeconds, toTransparency, timing, style);
}

int TweenTransparency(this Object*, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
  return TweenObjectTransparency(this, timeInSeconds, toTransparency, timing, style);
}

int TweenTransparency(this Character*, float timeInSeconds, short toTransparency, TweenTiming timing, TweenStyle style) {
  return TweenCharacterTransparency(this, timeInSeconds, toTransparency, timing, style);
}

void StopAllTweens(this GUI*) {
  TweenStopAllForGUI(this);
}

void StopAllTweens(this Object*) {
  TweenStopAllForObject(this);
}

void StopAllTweens(this Character*) {
  TweenStopAllForCharacter(this);
}

int TweenSize(this GUI*, float timeInSeconds, short toWidth, short toHeight, TweenTiming timing, TweenStyle style) {
  return TweenGUISize(this, timeInSeconds, toWidth, toHeight, timing, style);
}

int TweenScaling(this Character*, float timeInSeconds, short toScaling, TweenTiming timing, TweenStyle style) {
  return TweenCharacterScaling(this, timeInSeconds, toScaling, timing, style);
}
#endif
#endif

///////////////////////////////////////////////////////////////////////////////

function _TweenData::step(float amount) {
  // GUI step
  if (this.type == _eTweenGUIPosition) {
		this.guiRef.SetPosition(Lerp(this.fromX, this.toX, amount), Lerp(this.fromY, this.toY, amount));
	}
	if (this.type == _eTweenGUISize) {
		this.guiRef.SetSize(Lerp(this.fromX, this.toX, amount), Lerp(this.fromY, this.toY, amount));
	}
	else if (this.type == _eTweenGUITransparency) {
	  // Workaround for Popup Modal GUIs. If the scripter is fading this in, then make it vsible.
	  if (this.frameCount == 0 && this.guiRef.Visible == false && this.guiRef.Transparency == 100) {
	    this.guiRef.Visible = true;
	  }
	  
		this.guiRef.Transparency = Lerp(this.fromX, this.toX, amount);
		
		// Workaround for Popup Modal GUIs. If the scripter is fading this out, then make it invisble.
		if (this.frameCount == this.duration && this.guiRef.Visible == true && this.guiRef.Transparency == 100) {
			this.guiRef.Visible = false;
		}
	}
	/// OBJECT step
  else if (this.type == _eTweenObjectPosition) {
		this.objectRef.SetPosition(Lerp(this.fromX, this.toX, amount), Lerp(this.fromY, this.toY, amount));
	}
	else if (this.type == _eTweenObjectTransparency) {
	  if (this.frameCount == 0 && this.objectRef.Visible == false && this.objectRef.Transparency == 100) {
	    this.objectRef.Visible = true;
	  }
	  
		this.objectRef.Transparency = Lerp(this.fromX, this.toX, amount);
		
		if (this.frameCount == this.duration && this.objectRef.Visible == true && this.objectRef.Transparency == 100) {
			this.objectRef.Visible = false;
		}
	}	
	/// CHARACTER step
  else if (this.type == _eTweenCharacterPosition) {
		this.characterRef.x = Lerp(this.fromX, this.toX, amount);
		this.characterRef.y = Lerp(this.fromY, this.toY, amount);
	}
	else if (this.type == _eTweenCharacterScaling) {	  
	  this.characterRef.Scaling = Lerp(this.fromX, this.toX, amount);
	}
	else if (this.type == _eTweenCharacterTransparency) { 
		this.characterRef.Transparency = Lerp(this.fromX, this.toX, amount);
	}
}

void ResetAndStopAllTweens() {
  short i = 0;
  
  while (i < MAX_TWEENS) {
    if (_tweens[i].duration != -1) {
      _tweens[i].step(0.0);
      _tweens[i].duration = -1;
    }
    
    i++;
  }
}

///////////////////////////////////////////////////////////////////////////////

function game_start()
{
  short i = 0;
  
  while (i < MAX_TWEENS) {
    _tweens[i].duration = -1;
    i++;
  }
  
  System.VSync = true;
}

// This is where the magic happens:
function repeatedly_execute_always() {
  short i = 0;
  
  while (i < MAX_TWEENS) {
		if (_tweens[i].duration > 0) {
			float amount = ComputeTiming(
				_tweens[i].frameCount,
				_tweens[i].duration,
				_tweens[i].timing
				);
			
			_tweens[i].step(amount);
			
			_tweens[i].frameCount++;
			
			if (_tweens[i].frameCount > _tweens[i].duration) {
			  if (_tweens[i].style == eRepeatTween) {
					_tweens[i].frameCount = 0;
			  }
			  else if (_tweens[i].style == eReverseRepeatTween) {
					short fromX = _tweens[i].toX;
					short fromY = _tweens[i].toY;
					
					_tweens[i].toX = _tweens[i].fromX;
					_tweens[i].toY = _tweens[i].fromY;
					_tweens[i].fromX = fromX;
					_tweens[i].fromY = fromY;
					
					_tweens[i].frameCount = 0;
        }
				else {
					_tweens[i].duration = -1;
				}
			}
		}
		
		i++;
	}
}

function on_event(EventType event, int data) {
  if (event == eEventLeaveRoom) {
    ResetAndStopAllTweens();
  }
}